package com.black.framework.web.service;

import cn.hutool.core.util.StrUtil;
import com.black.app.domain.AppUser;
import com.black.app.service.AppUserService;
import com.black.common.constant.CacheConstants;
import com.black.common.constant.Constants;
import com.black.common.core.domain.req.AppLoginReq;
import com.black.common.core.domain.resp.AppUserDetails;
import com.black.common.exception.ServiceException;
import com.black.common.exception.user.BlackListException;
import com.black.common.exception.user.CaptchaException;
import com.black.common.exception.user.CaptchaExpireException;
import com.black.common.exception.user.UserPasswordNotMatchException;
import com.black.common.utils.MessageUtils;
import com.black.common.utils.ip.IpUtils;
import com.black.framework.manager.AsyncManager;
import com.black.framework.manager.factory.AsyncTimerTaskFactory;
import com.black.system.service.SysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Objects;

/**
 * 后台登录业务逻辑类
 */
@Component
public class AppLoginService {

    /**
     * 令牌管理对象
     */
    @Autowired
    private AppTokenService appTokenService;

    /**
     * spring security 认证管理器
     */
    @Qualifier("appAuthenticationManager")
    @Autowired
    private AuthenticationManager appAuthenticationManager;

    /**
     * redis 操作对象
     */
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 引用sysConfig业务逻辑层
     */
    @Autowired
    private SysConfigService sysConfigService;

    /**
     * 引用app_user业务逻辑层
     */
    @Autowired
    private AppUserService appUserService;

    /**
     * app登录业务逻辑
     *
     * @param appLoginReq
     * @return
     */
    public String login(AppLoginReq appLoginReq) {
        //判断登录类型
        if (appLoginReq.getType() == 1) {
            //1、判断图片验证码是否正确
            this.checkCaptcha(appLoginReq.getUuid(), appLoginReq.getImgCode());
        } else {
            //2、验证手机端验证码
            this.checkMobileCode(appLoginReq.getPhone(), appLoginReq.getMobileCode());
        }


        //2、登录判断，当前IP是不是黑白单中
        String blackIp = this.sysConfigService.getConfigValueByKey("sys.login.blackIPList");
        // 判断当前IP是否在黑名单中
        if (IpUtils.isMatchedIp(blackIp, IpUtils.getIpAddr())) {
            throw new BlackListException();
        }

        //存放到线程中
        AppUserThreadLocal.APP_USER_THREAD_LOCAL.set(appLoginReq);


        // 3、使用spring security框架，按它的规范来写登录代码
        // 认证接口
        Authentication authentication = null;

        try {
            String dbPassword = appLoginReq.getPassword();
            //手机号码登录
            if (appLoginReq.getType() == 2) {
                //查询手机号对应的密码
                AppUser dbUser = this.appUserService.findByPhone(appLoginReq.getPhone());
                if (Objects.isNull(dbUser)) {
                    throw new ServiceException("手机号未注册");
                }
                dbPassword=dbUser.getPassword();
            }

            //spring security给我配置的登录类
            UsernamePasswordAuthenticationToken usernamePasswordToken =
                    new UsernamePasswordAuthenticationToken(appLoginReq.getPhone(), dbPassword);

            //调用登录流程,调用自定义的 UserDetailsService接口
            authentication = this.appAuthenticationManager.authenticate(usernamePasswordToken);

        } catch (Exception e) {
            // 密码失败的异常
            if (e instanceof org.springframework.security.authentication.BadCredentialsException) {
                //记录登录失败原因到sys_logininfor表中
                AsyncManager.me().execute(AsyncTimerTaskFactory.recordLogininfor(appLoginReq.getPhone(),
                        Constants.LOGIN_FAIL,
                        MessageUtils.message("user.password.not.match")));

                throw new UserPasswordNotMatchException();
            } else {
                //记录登录失败原因到sys_logininfor表中
                AsyncManager.me().execute(AsyncTimerTaskFactory.recordLogininfor(appLoginReq.getPhone(),
                        Constants.LOGIN_FAIL,
                        e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }

        // 认证成功了, 写入登录志，更改最新登录时间 以及 生成令牌

        // 1、向登录日志表记录成功信息
        AsyncManager.me().execute(AsyncTimerTaskFactory.recordLogininfor(appLoginReq.getPhone(),
                Constants.LOGIN_SUCCESS,
                MessageUtils.message("user.login.success")));

        // 得到当前登录的用户
        AppUserDetails appUserDetails = (AppUserDetails) authentication.getPrincipal();

        //2、记录最后登录时间和ip
        AppUser updateUser = new AppUser();
        updateUser.setId(appUserDetails.getUserId());
        updateUser.setLoginIp(appUserDetails.getIpaddr());
        updateUser.setLoginDate(new Date());
        updateUser.setUpdateBy(appLoginReq.getPassword());
        updateUser.setUpdateTime(new Date());
        this.appUserService.updateById(updateUser);

        //3、换令牌
        String token = this.appTokenService.createToken(appUserDetails);

        return token;
    }

    /**
     * 判断手机号验证码是否为空
     *
     * @param phone
     * @param mobileCode
     */
    private void checkMobileCode(String phone, String mobileCode) {
        if (StrUtil.isBlank(mobileCode)) {
            throw new ServiceException(MessageUtils.message("notBlank.code"));
        }

        String redisKey = CacheConstants.SMS_LOGIN_CODE_KEY + phone;
        //获取redis中的验证码
        Object redisValue = this.redisTemplate.opsForValue().get(redisKey);
        // 如果为null
        if (Objects.isNull(redisValue)) {
            throw new CaptchaExpireException();
        }

        // 从redis中删除验证码
        this.redisTemplate.delete(redisKey);

        // 判断验证码是否正确
        if (!mobileCode.equals(redisValue)) {
            throw new CaptchaException();
        }

    }

    /**
     * 判断验证码是否正确
     *
     * @param uuid
     * @param code
     */
    public void checkCaptcha(String uuid, String code) {

        if (StrUtil.isBlank(code)) {
            throw new ServiceException(MessageUtils.message("notBlank.code"));
        }

        if (StrUtil.isBlank(uuid)) {
            throw new ServiceException(MessageUtils.message("notBlank.uuid"));
        }


        String redisKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
        //获取redis中的验证码
        Object redisValue = this.redisTemplate.opsForValue().get(redisKey);
        // 如果为null
        if (Objects.isNull(redisValue)) {
            throw new CaptchaExpireException();
        }

        // 从redis中删除验证码
        this.redisTemplate.delete(redisKey);

        // 判断验证码是否正确
        if (!code.equals(redisValue)) {
            throw new CaptchaException();
        }
    }
}
